package org.cbioportal.legacy.service.impl;

import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.cbioportal.legacy.model.GenericAssayAdditionalProperty;
import org.cbioportal.legacy.model.GenericAssayData;
import org.cbioportal.legacy.model.GenericAssayMolecularAlteration;
import org.cbioportal.legacy.model.MolecularProfile;
import org.cbioportal.legacy.model.MolecularProfile.MolecularAlterationType;
import org.cbioportal.legacy.model.MolecularProfileSamples;
import org.cbioportal.legacy.model.Sample;
import org.cbioportal.legacy.model.meta.GenericAssayMeta;
import org.cbioportal.legacy.persistence.GenericAssayRepository;
import org.cbioportal.legacy.persistence.MolecularDataRepository;
import org.cbioportal.legacy.persistence.PersistenceConstants;
import org.cbioportal.legacy.service.MolecularProfileService;
import org.cbioportal.legacy.service.SampleService;
import org.cbioportal.legacy.service.exception.GenericAssayNotFoundException;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner;

@RunWith(MockitoJUnitRunner.Silent.class)
public class GenericAssayServiceImpTest extends BaseServiceImplTest {

  public static final String GENERIC_ASSAY_ID_1 = "generic_assay_id_1";
  public static final String STUDY_ID_1 = "study_id_1";
  private static final int INTERNAL_ID_1 = 1;
  public static final String GENERIC_ASSAY_ID_2 = "generic_assay_id_2";
  public static final String STUDY_ID_2 = "study_id_2";
  private static final int INTERNAL_ID_2 = 2;
  public static final String ENTITY_TYPE = "GENERIC_ASSAY";

  private static final String PROFILE_ID = "test_profile_id";
  private static final List<String> PROFILE_ID_LIST = Arrays.asList(PROFILE_ID);
  private static final List<String> idList = Arrays.asList(GENERIC_ASSAY_ID_1, GENERIC_ASSAY_ID_2);
  private static final List<GenericAssayMeta> mockGenericAssayMetaList =
      createGenericAssayMetaList();
  private static final List<GenericAssayAdditionalProperty> mockGenericAssayAdditionalPropertyList =
      createGenericAssayAdditionalPropertyList();
  private static final String MOLECULAR_PROFILE_ID_1 = "molecular_profile_id_1";
  private static final String MOLECULAR_PROFILE_ID_2 = "molecular_profile_id_2";
  private static final String STABLE_ID_1 = "stable_id_1";
  private static final String STABLE_ID_2 = "stable_id_2";

  private static final String PROPERTY_NAME_1 = "property_name_1";
  private static final String PROPERTY_NAME_2 = "property_name_2";
  private static final String PROPERTY_VALUE_1 = "property_value_1";
  private static final String PROPERTY_VALUE_2 = "property_value_2";

  @InjectMocks private GenericAssayServiceImpl genericAssayService;

  @Mock private GenericAssayRepository genericAssayRepository;
  @Mock private SampleService sampleService;
  @Mock private MolecularProfileService geneticProfileService;
  @Mock private MolecularDataRepository geneticDataRepository;

  /**
   * This is executed n times, for each of the n test methods below:
   *
   * @throws Exception
   * @throws DaoException
   */
  @Before
  public void setUp() throws Exception {

    MolecularProfileSamples molecularProfileSamples1 = new MolecularProfileSamples();
    molecularProfileSamples1.setMolecularProfileId(MOLECULAR_PROFILE_ID_1);
    molecularProfileSamples1.setCommaSeparatedSampleIds("1,2,");

    MolecularProfileSamples molecularProfileSamples2 = new MolecularProfileSamples();
    molecularProfileSamples2.setMolecularProfileId(MOLECULAR_PROFILE_ID_2);
    molecularProfileSamples2.setCommaSeparatedSampleIds("1,2,");

    Map<String, MolecularProfileSamples> commaSeparatedSampleIdsOfMolecularProfilesMap1 =
        new HashMap<>();
    commaSeparatedSampleIdsOfMolecularProfilesMap1.put(
        MOLECULAR_PROFILE_ID_1, molecularProfileSamples1);

    Map<String, MolecularProfileSamples> commaSeparatedSampleIdsOfMolecularProfilesMap2 =
        new HashMap<>();
    commaSeparatedSampleIdsOfMolecularProfilesMap2.put(
        MOLECULAR_PROFILE_ID_1, molecularProfileSamples1);
    commaSeparatedSampleIdsOfMolecularProfilesMap2.put(
        MOLECULAR_PROFILE_ID_2, molecularProfileSamples2);

    // stub for samples
    Mockito.when(
            geneticDataRepository.commaSeparatedSampleIdsOfMolecularProfilesMap(
                Collections.singleton(MOLECULAR_PROFILE_ID_1)))
        .thenReturn(commaSeparatedSampleIdsOfMolecularProfilesMap1);
    Mockito.when(
            geneticDataRepository.commaSeparatedSampleIdsOfMolecularProfilesMap(
                Stream.of(MOLECULAR_PROFILE_ID_1, MOLECULAR_PROFILE_ID_2)
                    .collect(Collectors.toSet())))
        .thenReturn(commaSeparatedSampleIdsOfMolecularProfilesMap2);

    List<Sample> sampleList1 = new ArrayList<>();
    Sample sample = new Sample();
    sample.setCancerStudyIdentifier(STUDY_ID);
    sample.setInternalId(INTERNAL_ID_1);
    sample.setStableId(SAMPLE_ID1);
    sampleList1.add(sample);
    Mockito.when(
            sampleService.fetchSamples(Arrays.asList(STUDY_ID), Arrays.asList(SAMPLE_ID1), "ID"))
        .thenReturn(sampleList1);
    List<Sample> sampleListAll = new ArrayList<>(sampleList1);
    sample = new Sample();
    sample.setCancerStudyIdentifier(STUDY_ID);
    sample.setInternalId(INTERNAL_ID_2);
    sample.setStableId(SAMPLE_ID2);
    sampleListAll.add(sample);
    Mockito.when(
            sampleService.fetchSamples(
                Arrays.asList(STUDY_ID), Arrays.asList(SAMPLE_ID1, SAMPLE_ID2), "ID"))
        .thenReturn(sampleListAll);
    Mockito.when(
            sampleService.fetchSamples(
                Arrays.asList(STUDY_ID, STUDY_ID), Arrays.asList(SAMPLE_ID1, SAMPLE_ID2), "ID"))
        .thenReturn(sampleListAll);

    // stub for genetic profile
    MolecularProfile geneticProfile1 = new MolecularProfile();
    geneticProfile1.setCancerStudyIdentifier(STUDY_ID);
    geneticProfile1.setStableId(MOLECULAR_PROFILE_ID_1);
    geneticProfile1.setMolecularAlterationType(MolecularAlterationType.GENERIC_ASSAY);
    MolecularProfile geneticProfile2 = new MolecularProfile();
    geneticProfile2.setCancerStudyIdentifier(STUDY_ID);
    geneticProfile2.setStableId(MOLECULAR_PROFILE_ID_2);
    geneticProfile2.setMolecularAlterationType(MolecularAlterationType.GENERIC_ASSAY);
    List<MolecularProfile> geneticProfiles = new ArrayList<MolecularProfile>();
    geneticProfiles.add(geneticProfile1);
    geneticProfiles.add(geneticProfile2);

    Mockito.when(geneticProfileService.getMolecularProfile(MOLECULAR_PROFILE_ID_1))
        .thenReturn(geneticProfile1);
    Mockito.when(
            geneticProfileService.getMolecularProfiles(
                Collections.singleton(MOLECULAR_PROFILE_ID_1), "SUMMARY"))
        .thenReturn(Arrays.asList(geneticProfile1));
    Mockito.when(
            geneticProfileService.getMolecularProfiles(
                Stream.of(MOLECULAR_PROFILE_ID_1, MOLECULAR_PROFILE_ID_2)
                    .collect(Collectors.toSet()),
                "SUMMARY"))
        .thenReturn(geneticProfiles);

    // stub for repository data
    List<GenericAssayMolecularAlteration> genericAssayMolecularAlterationList1 = new ArrayList<>();

    GenericAssayMolecularAlteration genericAssayMolecularAlteration1 =
        new GenericAssayMolecularAlteration();
    genericAssayMolecularAlteration1.setMolecularProfileId(MOLECULAR_PROFILE_ID_1);
    genericAssayMolecularAlteration1.setGenericAssayStableId(STABLE_ID_1);
    genericAssayMolecularAlteration1.setValues("0.2,0.499");

    GenericAssayMolecularAlteration genericAssayMolecularAlteration2 =
        new GenericAssayMolecularAlteration();
    genericAssayMolecularAlteration2.setMolecularProfileId(MOLECULAR_PROFILE_ID_1);
    genericAssayMolecularAlteration2.setGenericAssayStableId(STABLE_ID_2);
    genericAssayMolecularAlteration2.setValues("0.89,-0.509");

    genericAssayMolecularAlterationList1.add(genericAssayMolecularAlteration1);
    genericAssayMolecularAlterationList1.add(genericAssayMolecularAlteration2);

    List<GenericAssayMolecularAlteration> genericAssayMolecularAlterationList2 = new ArrayList<>();

    genericAssayMolecularAlteration1 = new GenericAssayMolecularAlteration();
    genericAssayMolecularAlteration1.setMolecularProfileId(MOLECULAR_PROFILE_ID_2);
    genericAssayMolecularAlteration1.setGenericAssayStableId(STABLE_ID_1);
    genericAssayMolecularAlteration1.setValues("0.2,0.499");

    genericAssayMolecularAlteration2 = new GenericAssayMolecularAlteration();
    genericAssayMolecularAlteration2.setMolecularProfileId(MOLECULAR_PROFILE_ID_2);
    genericAssayMolecularAlteration2.setGenericAssayStableId(STABLE_ID_2);
    genericAssayMolecularAlteration2.setValues("0.89,-0.509");

    genericAssayMolecularAlterationList2.add(genericAssayMolecularAlteration1);
    genericAssayMolecularAlterationList2.add(genericAssayMolecularAlteration2);

    List<Integer> entrezGeneIds = new ArrayList<>();
    entrezGeneIds.add(ENTREZ_GENE_ID_1);
    Mockito.when(
            geneticDataRepository.getGenericAssayMolecularAlterations(
                MOLECULAR_PROFILE_ID_1, Arrays.asList(STABLE_ID_1, STABLE_ID_2), "SUMMARY"))
        .thenReturn(genericAssayMolecularAlterationList1);
    Mockito.when(
            geneticDataRepository.getGenericAssayMolecularAlterations(
                MOLECULAR_PROFILE_ID_2, Arrays.asList(STABLE_ID_1, STABLE_ID_2), "SUMMARY"))
        .thenReturn(genericAssayMolecularAlterationList2);
  }

  @Test
  public void fetchGenericAssayDataInMultipleMolecularProfiles() throws Exception {

    List<GenericAssayData> result =
        genericAssayService.fetchGenericAssayData(
            Arrays.asList(MOLECULAR_PROFILE_ID_1, MOLECULAR_PROFILE_ID_2),
            Arrays.asList(SAMPLE_ID1, SAMPLE_ID2),
            Arrays.asList(STABLE_ID_1, STABLE_ID_2),
            PersistenceConstants.SUMMARY_PROJECTION);

    // what we expect: 2 molecular profiles x 2 samples x 2 generic assay items = 8 GenericAssayData
    // items:
    // MOLECULAR_PROFILE_1:
    //     SAMPLE_1:
    //         generic assay1 value: 0.2
    //         generic assay2 value: 0.89
    //     SAMPLE_2:
    //         generic assay1 value: 0.499
    //         generic assay2 value: -0.509
    // MOLECULAR_PROFILE_2:
    //     SAMPLE_1:
    //         generic assay1 value: 0.2
    //         generic assay2 value: 0.89
    //     SAMPLE_2:
    //         generic assay1 value: 0.499
    //         generic assay2 value: -0.509
    Assert.assertEquals(8, result.size());
    GenericAssayData item1 = result.get(0);
    Assert.assertEquals(item1.getSampleId(), SAMPLE_ID1);
    Assert.assertEquals(item1.getStableId(), STABLE_ID_1);
    Assert.assertEquals(item1.getValue(), "0.2");
    Assert.assertEquals(item1.getMolecularProfileId(), MOLECULAR_PROFILE_ID_1);
    GenericAssayData item2 = result.get(1);
    Assert.assertEquals(item2.getSampleId(), SAMPLE_ID1);
    Assert.assertEquals(item2.getStableId(), STABLE_ID_2);
    Assert.assertEquals(item2.getValue(), "0.89");
    Assert.assertEquals(item2.getMolecularProfileId(), MOLECULAR_PROFILE_ID_1);
    GenericAssayData item7 = result.get(6);
    Assert.assertEquals(item7.getSampleId(), SAMPLE_ID2);
    Assert.assertEquals(item7.getStableId(), STABLE_ID_1);
    Assert.assertEquals(item7.getValue(), "0.499");
    Assert.assertEquals(item7.getMolecularProfileId(), MOLECULAR_PROFILE_ID_2);
    GenericAssayData item8 = result.get(7);
    Assert.assertEquals(item8.getSampleId(), SAMPLE_ID2);
    Assert.assertEquals(item8.getStableId(), STABLE_ID_2);
    Assert.assertEquals(item8.getValue(), "-0.509");
    Assert.assertEquals(item8.getMolecularProfileId(), MOLECULAR_PROFILE_ID_2);
  }

  @Test
  public void fetchGenericAssayData() throws Exception {

    List<GenericAssayData> result =
        genericAssayService.fetchGenericAssayData(
            MOLECULAR_PROFILE_ID_1,
            Arrays.asList(SAMPLE_ID1, SAMPLE_ID2),
            Arrays.asList(STABLE_ID_1, STABLE_ID_2),
            PersistenceConstants.SUMMARY_PROJECTION);

    // what we expect: 2 samples x 2 generic assay items = 4 GenericAssayData items:
    // SAMPLE_1:
    //   generic assay1 value: 0.2
    //   generic assay2 value: 0.89
    // SAMPLE_2:
    //   generic assay1 value: 0.499
    //   generic assay2 value: -0.509
    Assert.assertEquals(4, result.size());
    GenericAssayData item1 = result.get(0);
    Assert.assertEquals(item1.getSampleId(), SAMPLE_ID1);
    Assert.assertEquals(item1.getStableId(), STABLE_ID_1);
    Assert.assertEquals(item1.getValue(), "0.2");
    Assert.assertEquals(item1.getMolecularProfileId(), MOLECULAR_PROFILE_ID_1);
    GenericAssayData item2 = result.get(1);
    Assert.assertEquals(item2.getSampleId(), SAMPLE_ID1);
    Assert.assertEquals(item2.getStableId(), STABLE_ID_2);
    Assert.assertEquals(item2.getValue(), "0.89");
    Assert.assertEquals(item2.getMolecularProfileId(), MOLECULAR_PROFILE_ID_1);
    GenericAssayData item4 = result.get(3);
    Assert.assertEquals(item4.getSampleId(), SAMPLE_ID2);
    Assert.assertEquals(item4.getStableId(), STABLE_ID_2);
    Assert.assertEquals(item4.getValue(), "-0.509");
    Assert.assertEquals(item4.getMolecularProfileId(), MOLECULAR_PROFILE_ID_1);

    // check when selecting only 1 sample:
    result =
        genericAssayService.fetchGenericAssayData(
            MOLECULAR_PROFILE_ID_1,
            Arrays.asList(SAMPLE_ID1),
            Arrays.asList(STABLE_ID_1, STABLE_ID_2),
            PersistenceConstants.SUMMARY_PROJECTION);
    Assert.assertEquals(2, result.size());
    item1 = result.get(0);
    Assert.assertEquals(item1.getSampleId(), SAMPLE_ID1);
    Assert.assertEquals(item1.getStableId(), STABLE_ID_1);
    Assert.assertEquals(item1.getValue(), "0.2");
    Assert.assertEquals(item1.getMolecularProfileId(), MOLECULAR_PROFILE_ID_1);
  }

  @Test
  public void getGenericAssayMetaByStableIdsAndMolecularIds() throws GenericAssayNotFoundException {
    Mockito.when(genericAssayRepository.getGenericAssayMeta(idList))
        .thenReturn(mockGenericAssayMetaList);

    Mockito.when(genericAssayRepository.getGenericAssayStableIdsByMolecularIds(PROFILE_ID_LIST))
        .thenReturn(idList);

    Mockito.when(genericAssayRepository.getGenericAssayAdditionalproperties(idList))
        .thenReturn(mockGenericAssayAdditionalPropertyList);

    List<GenericAssayMeta> result =
        genericAssayService.getGenericAssayMetaByStableIdsAndMolecularIds(
            idList, PROFILE_ID_LIST, PersistenceConstants.SUMMARY_PROJECTION);
    GenericAssayMeta meta1 = result.get(0);
    GenericAssayMeta meta2 = result.get(1);
    Assert.assertEquals(mockGenericAssayMetaList.get(0).getEntityType(), meta1.getEntityType());
    Assert.assertEquals(mockGenericAssayMetaList.get(0).getStableId(), meta1.getStableId());
    Assert.assertEquals(
        mockGenericAssayMetaList.get(0).getGenericEntityMetaProperties(),
        meta1.getGenericEntityMetaProperties());

    Assert.assertEquals(mockGenericAssayMetaList.get(1).getEntityType(), meta2.getEntityType());
    Assert.assertEquals(mockGenericAssayMetaList.get(1).getStableId(), meta2.getStableId());
    Assert.assertEquals(
        mockGenericAssayMetaList.get(1).getGenericEntityMetaProperties(),
        meta2.getGenericEntityMetaProperties());
  }

  private static List<GenericAssayMeta> createGenericAssayMetaList() {

    List<GenericAssayMeta> genericAssayMetaList = new ArrayList<>();

    GenericAssayMeta meta1 = new GenericAssayMeta(GENERIC_ASSAY_ID_1, ENTITY_TYPE);
    HashMap<String, String> map1 = new HashMap<String, String>();
    map1.put(PROPERTY_NAME_1, PROPERTY_VALUE_1);
    meta1.setGenericEntityMetaProperties(map1);
    genericAssayMetaList.add(meta1);

    GenericAssayMeta meta2 = new GenericAssayMeta(GENERIC_ASSAY_ID_2, ENTITY_TYPE);
    HashMap<String, String> map2 = new HashMap<String, String>();
    map2.put(PROPERTY_NAME_2, PROPERTY_VALUE_2);
    meta2.setGenericEntityMetaProperties(map2);
    genericAssayMetaList.add(meta2);
    return genericAssayMetaList;
  }

  private static List<GenericAssayAdditionalProperty> createGenericAssayAdditionalPropertyList() {

    List<GenericAssayAdditionalProperty> genericAssayAdditionalPropertyList = new ArrayList<>();

    GenericAssayAdditionalProperty property1 =
        new GenericAssayAdditionalProperty(PROPERTY_NAME_1, PROPERTY_VALUE_1, GENERIC_ASSAY_ID_1);
    genericAssayAdditionalPropertyList.add(property1);

    GenericAssayAdditionalProperty property2 =
        new GenericAssayAdditionalProperty(PROPERTY_NAME_2, PROPERTY_VALUE_2, GENERIC_ASSAY_ID_2);
    genericAssayAdditionalPropertyList.add(property2);
    return genericAssayAdditionalPropertyList;
  }
}
